package com.fecean.aabbc.netty.client.prod7;

import com.fecean.aabbc.netty.message7.HeartbeatMessage;
import com.fecean.aabbc.netty.message7.MessageBuilder;
import com.fecean.aabbc.netty.message7.MessageType;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 ** Netty客户端心跳处理
 *
 * @author: chenfenghai
 * @version: 1.0
 * @date: 2020-03-17 15:59
 * @blame Team
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyClientHeartbeatHandler extends ChannelInboundHandlerAdapter {

    /**
     ** Netty客户端启动器
     */
    @Resource
    NettyClientStarter nettyClientStarter;

    /**
     ** 影院属性配置
     */
    @Resource
    CinemaConfig cinemaConfig;

    /**
     ** 心跳计数器
     */
    private int idleHeartbeatCount = 0;

    /**
     ** 心跳重连最大值
     */
    private static final int IDLE_HEARTBEAT_MAX_COUNT = 10;

    /**
     ** 读取数据处理
     *
     * @param ctx 通道上下文
     * @param msg 读取到的数据
     * @throws Exception 处理过程中存在异常则抛出
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        MessageBuilder messageBuilder = MessageBuilder.of(msg);
        if(messageBuilder.isMessage(MessageType.HEARTBEAT)){
            log.info("客户端[{}-{}]收到心跳",
                    messageBuilder.getMessage().getTenantId(),
                    messageBuilder.getMessage().getCinemaCode());
            // 收到心跳响应，重置计数器为0
            idleHeartbeatCount = 0;
            return;
        }
        ctx.fireChannelRead(msg);
    }

    /**
     ** 客户端失去连接处理，重新连接服务器
     *
     * @param ctx 通道上下文
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        // 重新连接服务器
        nettyClientStarter.reconnect();
    }

    /**
     ** 心跳机制，事件触发处理，如果N秒内没有读或写操作，则发送心跳信息
     *
     * @param ctx 通道上下文
     * @param evt 心跳事件
     * @throws Exception 处理过程中存在异常则抛出
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 不是心跳事件类型，则跳过
        if (!(evt instanceof IdleStateEvent)) {
            ctx.fireUserEventTriggered(evt);
            return;
        }
        // 不是读写超时心跳类型，则跳过
        IdleState state = ((IdleStateEvent) evt).state();
        if (IdleState.ALL_IDLE.compareTo(state) != 0) {
            ctx.fireUserEventTriggered(evt);
            return;
        }
        // 心跳计数器超过最大值时，关闭当前netty channel
        // 触发channelInactive事件，进行重新连接
        if(idleHeartbeatCount > IDLE_HEARTBEAT_MAX_COUNT){
            log.info("客户端[{}-{}]心跳超过最大值[{}]，将进行重新连接",
                    cinemaConfig.getTenantId(), cinemaConfig.getCinemaCode(), idleHeartbeatCount);
            ctx.channel().close();
            return;
        }
        // 发送心跳信息
        HeartbeatMessage message = MessageBuilder.buildHeartbeatMessage(
                cinemaConfig.getTenantId(), cinemaConfig.getCinemaCode());
        ctx.writeAndFlush(message.toDataObject());
        // 心跳计数器加一
        idleHeartbeatCount++;
        ctx.fireUserEventTriggered(evt);
    }

    /**
     ** 报错 处理事件
     *
     * @param ctx 通道上下文
     * @param cause 异常
     * @throws Exception 处理过程中存在异常则抛出
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("", cause);
        ctx.fireExceptionCaught(cause);
    }
}
